#include <chrono>
#include <ctime>

#include "log.h"
#include "VirtFoldersMgr.h"

#define SHARE_FIRST_ACCESS_TIMEOUT 10
#define SHARE_SUBSEQ_ACCESS_TIMEOUT 30

#define SHARE_CLEANUP_PERIOD_MS 5000

#define CB_SHARE_PREFIX "clipboard_"

VirtFoldersMgr::VirtFoldersMgr()
    : m_bInitialised(FALSE),
      m_fuseServerIFace(nullptr),
      m_cleanupThread(nullptr),
      m_cleanupEndEvt(nullptr)
{}

VirtFoldersMgr::~VirtFoldersMgr()
{
    Destroy();
}

DWORD VirtFoldersMgr::Init(IFSL* fuseServerIFace)
{
    if (fuseServerIFace == nullptr) {
        LOGE("Fuse server Interface is a null pointer");
        return ERROR_INVALID_DATA;
    }

    m_fuseServerIFace = fuseServerIFace;
    DWORD err = ERROR_SUCCESS;

    do {
        m_cleanupEndEvt = CreateEvent(nullptr, FALSE, FALSE, nullptr);
        if (!m_cleanupEndEvt) {
            err = GetLastError();
            LOGE("CreateEvent failed with error: {}", err);
            break;
        }

        m_cleanupThread = CreateThread(nullptr, 0, (LPTHREAD_START_ROUTINE)CleanupThread, (LPVOID)this, 0, nullptr);
        if (m_cleanupThread == nullptr) {
            err = GetLastError();
            LOGE("CreateThread failed with error: {}", err);
            CloseHandle(m_cleanupEndEvt);
            break;
        }

        m_bInitialised = TRUE;
    } while (0);

    return err;
}

void VirtFoldersMgr::Destroy()
{
    if (!m_bInitialised) {
        return;
    }

    SetEvent(m_cleanupEndEvt);
    if (WaitForSingleObject(m_cleanupThread, SHARE_CLEANUP_PERIOD_MS) == WAIT_TIMEOUT) {
        LOGW("Terminate Thread for Cleanup.");
        TerminateThread(m_cleanupThread, 0);
    }
    CloseHandle(m_cleanupThread);
    CloseHandle(m_cleanupEndEvt);
    m_bInitialised = FALSE;
}

std::string VirtFoldersMgr::AddCBVirtFolder(const FileShareList& fileShares)
{
    return AddVirtFolder(CB_SHARE_PREFIX, fileShares);
}


std::string VirtFoldersMgr::AddVirtFolder(const char* shareNamePrefix, const FileShareList& fileShares)
{
    if (!m_bInitialised) {
        THROW_ERR(ERROR_DEVICE_NO_RESOURCES);
    }

    std::string name;
    std::string prefix(shareNamePrefix);
    uint64_t timeStamp = std::chrono::seconds(std::time(nullptr)).count();

    std::lock_guard<std::mutex> guard(m_activeSharesGuard);
    for (int i = 0;; i++) {
        name = prefix + std::to_string(timeStamp + i);
        if (m_activeShares.find(name) == m_activeShares.end()) {
            break;
        }
    }

    if (!m_fuseServerIFace->AddFileList(name, fileShares, timeStamp, true, true)) {
        THROW_ERR(ERROR_ALREADY_EXISTS);
    }

    m_activeShares.insert({name, std::pair(timeStamp, true)});

    auto lastShare = m_activeShares.find(m_lastActiveShare);
    if (lastShare != m_activeShares.end()) {
        lastShare->second.second = false;
    }
    m_lastActiveShare = name;

    return name;
}

DWORD WINAPI VirtFoldersMgr::CleanupThread(VirtFoldersMgr* ptr)
{
    while (WaitForSingleObject(ptr->m_cleanupEndEvt,
                               SHARE_CLEANUP_PERIOD_MS) == WAIT_TIMEOUT) {
        std::lock_guard<std::mutex> guard(ptr->m_activeSharesGuard);
        bool erase;
        for (auto it = ptr->m_activeShares.begin();
             it != ptr->m_activeShares.end(); erase ? it : it++) {
            erase = false;

            if (it->second.second) {
                continue;
            }

            uint64_t timeStamp = std::chrono::seconds(std::time(nullptr)).count();
            uint64_t addTime = it->second.first;
            uint64_t lastAccessTime;

            if (!ptr->m_fuseServerIFace->GetLastAccessTime(it->first, lastAccessTime)) {
                continue;
            }

            if (addTime == lastAccessTime && (timeStamp - addTime) < SHARE_FIRST_ACCESS_TIMEOUT) {
                continue;
            }
            if ((timeStamp - lastAccessTime) > SHARE_SUBSEQ_ACCESS_TIMEOUT &&
                ptr->m_fuseServerIFace->RemoveShare(it->first, false) == REMOVE_SHARE_SUCCESS) {
                it = ptr->m_activeShares.erase(it);
                erase = true;
            }
        }
    }
    return 0;
}
